﻿/*
Copyright © 2010 François Karman

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
OTHER DEALINGS IN THE SOFTWARE.

See <license.txt> for the complete license of the software.
*/

using System.Collections;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Windows;
using System.Windows.Controls;

namespace Faz.Dependencies.Controls
{
    /// <summary>
    /// Defines a view that can be used with <see cref="ListView"/> to display
    /// the elements as part of a dependency map.
    /// </summary>
    public class DependencyView : ViewBase
    {
        /// <summary>
        /// Identifies the <see cref="DependencyView.References"/> dependency property.
        /// </summary>
        public static readonly DependencyProperty ReferencesProperty =
            DependencyProperty.RegisterReadOnly("References", typeof(ICollection<object>), typeof(DependencyView), new PropertyMetadata(new ObservableCollection<object>())).DependencyProperty;

        /// <summary>
        /// Identifies the <see cref="DependencyView.DependencyPropertyName"/> dependency property.
        /// </summary>
        public static readonly DependencyProperty DependencyPropertyNameProperty =
            DependencyProperty.Register("DependencyPropertyName", typeof(string), typeof(DependencyView), new PropertyMetadata("Dependencies"));

        /// <summary>
        /// Identifies the <see cref="DependencyView.ResultContainerStyle"/> dependency property.
        /// </summary>
        public static readonly DependencyProperty ResultContainerStyleProperty =
            DependencyProperty.Register("ResultContainerStyle", typeof(Style), typeof(DependencyView));

        /// <summary>
        /// Identifies the <see cref="DependencyView.ResultStringFormat"/> dependency property.
        /// </summary>
        public static readonly DependencyProperty ResultStringFormatProperty =
            DependencyProperty.Register("ResultStringFormat", typeof(string), typeof(DependencyView));

        /// <summary>
        /// Identifies the <see cref="DependencyView.ResultTemplate"/> dependency property.
        /// </summary>
        public static readonly DependencyProperty ResultTemplateProperty =
            DependencyProperty.Register("ResultTemplate", typeof(DataTemplate), typeof(DependencyView));

        /// <summary>
        /// Identifies the <see cref="DependencyView.ResultTemplateSelector"/> dependency property.
        /// </summary>
        public static readonly DependencyProperty ResultTemplateSelectorProperty =
            DependencyProperty.Register("ResultTemplateSelector", typeof(DataTemplateSelector), typeof(DependencyView));

        /// <summary>
        /// Identifies the <see cref="DependencyView.ReferenceContainerStyle"/> dependency property.
        /// </summary>
        public static readonly DependencyProperty ReferenceContainerStyleProperty =
            DependencyProperty.Register("ReferenceContainerStyle", typeof(Style), typeof(DependencyView));

        /// <summary>
        /// Identifies the <see cref="DependencyView.ReferenceContainerStyleSelector"/> dependency property.
        /// </summary>
        public static readonly DependencyProperty ReferenceContainerStyleSelectorProperty =
            DependencyProperty.Register("ReferenceContainerStyleSelector", typeof(StyleSelector), typeof(DependencyView));

        /// <summary>
        /// Identifies the <see cref="DependencyView.ReferenceStringFormat"/> dependency property.
        /// </summary>
        public static readonly DependencyProperty ReferenceStringFormatProperty =
            DependencyProperty.Register("ReferenceStringFormat", typeof(string), typeof(DependencyView));

        /// <summary>
        /// Identifies the <see cref="DependencyView.ReferenceTemplate"/> dependency property.
        /// </summary>
        public static readonly DependencyProperty ReferenceTemplateProperty =
            DependencyProperty.Register("ReferenceTemplate", typeof(DataTemplate), typeof(DependencyView));

        /// <summary>
        /// Identifies the <see cref="DependencyView.ReferenceTemplateSelector"/> dependency property.
        /// </summary>
        public static readonly DependencyProperty ReferenceTemplateSelectorProperty =
            DependencyProperty.Register("ReferenceTemplateSelector", typeof(DataTemplateSelector), typeof(DependencyView));

        /// <summary>
        /// Identifies the <see cref="DependencyView.Corner"/> dependency property.
        /// </summary>
        public static readonly DependencyProperty CornerProperty =
            DependencyProperty.Register("Corner", typeof(FrameworkElement), typeof(DependencyView));

        /// <summary>
        /// Contains the number of time that an element is used as a reference.
        /// </summary>
        private IDictionary<object, int> referencesCounts;

        /// <summary>
        /// Initializes a new instance of the DependencyView class.
        /// </summary>
        public DependencyView()
        {
            Trace.Default.TraceVerbose("DependencyView.ctor()");
            this.referencesCounts = new Dictionary<object, int>();
        }

        /// <summary>
        /// Gets the list of all the reference elements.
        /// </summary>
        public ICollection<object> References
        {
            get { return (ICollection<object>)this.GetValue(ReferencesProperty); }
        }

        /// <summary>
        /// Gets or sets the name of the property used to extract the dependencies from
        /// the items of the list.
        /// </summary>
        [Bindable(true)]
        [Category("Content")]
        public string DependencyPropertyName
        {
            get { return (string)this.GetValue(DependencyPropertyNameProperty); }
            set { this.SetValue(DependencyPropertyNameProperty, value); }
        }

        /// <summary>
        /// Gets or sets the <see cref="Style"/> that is applied to the container element
        /// generated for each result.
        /// </summary>
        [Bindable(true)]
        [Category("Result")]
        public Style ResultContainerStyle
        {
            get { return (Style)this.GetValue(ResultContainerStyleProperty); }
            set { this.SetValue(ResultContainerStyleProperty, value); }
        }

        /// <summary>
        /// Gets or sets a composite string that specifies how to format the results
        /// if they are displayed as strings.
        /// </summary>
        [Bindable(true)]
        [Category("Result")]
        public string ResultStringFormat
        {
            get { return (string)this.GetValue(ResultStringFormatProperty); }
            set { this.SetValue(ResultStringFormatProperty, value); }
        }

        /// <summary>
        /// Gets or sets the <see cref="DataTemplate"/> used to display each result.
        /// </summary>
        [Bindable(true)]
        [Category("Result")]
        public DataTemplate ResultTemplate
        {
            get { return (DataTemplate)this.GetValue(ResultTemplateProperty); }
            set { this.SetValue(ResultTemplateProperty, value); }
        }

        /// <summary>
        /// Gets or sets the custom logic for choosing a template used to display each result.
        /// </summary>
        [Bindable(true)]
        [Category("Result")]
        [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
        public DataTemplateSelector ResultTemplateSelector
        {
            get { return (DataTemplateSelector)this.GetValue(ResultTemplateSelectorProperty); }
            set { this.SetValue(ResultTemplateSelectorProperty, value); }
        }

        /// <summary>
        /// Gets or sets the <see cref="Style"/> that is applied to the container element
        /// generated for each reference header.
        /// </summary>
        [Bindable(true)]
        [Category("Reference")]
        public Style ReferenceContainerStyle
        {
            get { return (Style)this.GetValue(ReferenceContainerStyleProperty); }
            set { this.SetValue(ReferenceContainerStyleProperty, value); }
        }

        /// <summary>
        /// Gets or sets custom style-selection logic for a style that can be applied to
        /// each generated reference header element.
        /// </summary>
        [Bindable(true)]
        [Category("Reference")]
        [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
        public StyleSelector ReferenceContainerStyleSelector
        {
            get { return (StyleSelector)this.GetValue(ReferenceContainerStyleSelectorProperty); }
            set { this.SetValue(ReferenceContainerStyleSelectorProperty, value); }
        }

        /// <summary>
        /// Gets or sets a composite string that specifies how to format the references
        /// if they are displayed as strings.
        /// </summary>
        [Bindable(true)]
        [Category("Reference")]
        public string ReferenceStringFormat
        {
            get { return (string)this.GetValue(ReferenceStringFormatProperty); }
            set { this.SetValue(ReferenceStringFormatProperty, value); }
        }

        /// <summary>
        /// Gets or sets the <see cref="DataTemplate"/> used to display each reference header.
        /// </summary>
        [Bindable(true)]
        [Category("Reference")]
        public DataTemplate ReferenceTemplate
        {
            get { return (DataTemplate)this.GetValue(ReferenceTemplateProperty); }
            set { this.SetValue(ReferenceTemplateProperty, value); }
        }

        /// <summary>
        /// Gets or sets the custom logic for choosing a template used to display each reference header.
        /// </summary>
        [Bindable(true)]
        [Category("Reference")]
        [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
        public DataTemplateSelector ReferenceTemplateSelector
        {
            get { return (DataTemplateSelector)this.GetValue(ReferenceTemplateSelectorProperty); }
            set { this.SetValue(ReferenceTemplateSelectorProperty, value); }
        }

        /// <summary>
        /// Gets or sets the element used as the corner of the table.
        /// </summary>
        [Bindable(true)]
        [Category("Content")]
        public FrameworkElement Corner
        {
            get { return (FrameworkElement)this.GetValue(CornerProperty); }
            set { this.SetValue(CornerProperty, value); }
        }

        /// <summary>
        /// Gets the key associated with the style of the <see cref="ListView"/>
        /// for the view mode.
        /// </summary>
        protected override object DefaultStyleKey
        {
            get { return new ComponentResourceKey(this.GetType(), "DefaultStyleKey"); }
        }

        /// <summary>
        /// Extracts all the dependencies associated with a value.
        /// </summary>
        /// <param name="value">The reference.</param>
        /// <returns>The dependencies of the reference.</returns>
        public IEnumerable<object> ExtractDependencies(object value)
        {
            if (value != null && this.DependencyPropertyName != null)
            {
                string dependencyPropertyName = this.DependencyPropertyName;
                object collection = value.GetType().GetProperty(dependencyPropertyName).GetValue(value, null);
                return collection as IEnumerable<object>;
            }
            else
            {
                return null;
            }
        }

        /// <summary>
        /// Called when a new item is added to the list - used to Update
        /// the <see cref="DependencyView.References" /> property.
        /// </summary>
        /// <param name="item">
        /// The new item.
        /// </param>
        protected override void PrepareItem(ListViewItem item)
        {
            base.PrepareItem(item);

            object value = item.Content;
            if (value == null)
            {
                return;
            }

            IEnumerable<object> deps = this.ExtractDependencies(value);
            if (deps == null)
            {
                return;
            }

            foreach (object d in deps)
            {
                if (!this.References.Contains(d))
                {
                    this.References.Add(d);
                    this.referencesCounts.Add(d, 1);
                }
                else
                {
                    this.referencesCounts[d]++;
                }
            }
        }

        /// <summary>
        /// Called when an item is removed from the list - used to Update
        /// the <see cref="DependencyView.References" /> property.
        /// </summary>
        /// <param name="item">
        /// The removed item.
        /// </param>
        protected override void ClearItem(ListViewItem item)
        {
            object value = item.Content;
            if (value == null)
            {
                return;
            }

            IEnumerable<object> deps = this.ExtractDependencies(value);
            if (deps == null)
            {
                return;
            }

            foreach (object d in deps)
            {
                this.referencesCounts[d]--;
                if (this.referencesCounts[d] <= 0)
                {
                    this.References.Remove(d);
                    this.referencesCounts.Remove(d);
                }
            }

            base.ClearItem(item);
        }
    }
}
